import numpy as np
import pandas as pd
import dalex as dx
import pickle
housing = pd.read_csv('housing_preprocessed.csv')
# xgb = pickle.load(open("xgb.pickle", 'rb'))
gb = pickle.load(open("gradient_boost.pickle", 'rb'))
housing.head()
| longitude | latitude | housing_median_age | total_bedrooms | population | households | median_income | <1H OCEAN | INLAND | NEAR BAY | NEAR OCEAN | rooms_per_household | median_house_value | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.211155 | 0.567481 | 0.784314 | 0.019711 | 0.008941 | 0.020395 | 0.539668 | 0.0 | 0.0 | 1.0 | 0.0 | 0.046610 | 452600.0 |
| 1 | 0.212151 | 0.565356 | 0.392157 | 0.171349 | 0.067210 | 0.186842 | 0.538027 | 0.0 | 0.0 | 1.0 | 0.0 | 0.040945 | 358500.0 |
| 2 | 0.210159 | 0.564293 | 1.000000 | 0.029179 | 0.013818 | 0.028783 | 0.466028 | 0.0 | 0.0 | 1.0 | 0.0 | 0.056513 | 352100.0 |
| 3 | 0.209163 | 0.564293 | 1.000000 | 0.036163 | 0.015555 | 0.035691 | 0.354699 | 0.0 | 0.0 | 1.0 | 0.0 | 0.037750 | 341300.0 |
| 4 | 0.209163 | 0.564293 | 1.000000 | 0.043148 | 0.015752 | 0.042270 | 0.230776 | 0.0 | 0.0 | 1.0 | 0.0 | 0.041277 | 342200.0 |
predictors = housing.drop('median_house_value', axis = 1)
target = housing.loc[:, ['median_house_value']]
observation = predictors.loc[[420], :]
gb.predict(observation)
array([386498.09536054])
Używamy profili Ceteris Paribus.
explainer = dx.Explainer(gb, predictors, target)
Preparation of a new explainer is initiated -> data : 19643 rows 12 cols -> target variable : Parameter 'y' was a pandas.DataFrame. Converted to a numpy.ndarray. -> target variable : 19643 values -> model_class : sklearn.ensemble._gb.GradientBoostingRegressor (default) -> label : Not specified, model's class short name will be used. (default) -> predict function : <function yhat_default at 0x00000205D51C7E50> will be used (default) -> predict function : Accepts pandas.DataFrame and numpy.ndarray. -> predicted values : min = 1.59e+04, mean = 1.92e+05, max = 5.08e+05 -> model type : regression will be used (default) -> residual function : difference between y and yhat (default) -> residuals : min = -2.62e+05, mean = -1.2e+02, max = 2.8e+05 -> model_info : package sklearn A new explainer has been created!
cp = explainer.predict_profile(observation)
Calculating ceteris paribus: 100%|████████████████████████████████████████████████████| 12/12 [00:00<00:00, 138.01it/s]
observation
| longitude | latitude | housing_median_age | total_bedrooms | population | households | median_income | <1H OCEAN | INLAND | NEAR BAY | NEAR OCEAN | rooms_per_household | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 420 | 0.209163 | 0.568544 | 0.705882 | 0.070619 | 0.027579 | 0.073684 | 0.485414 | 0.0 | 0.0 | 1.0 | 0.0 | 0.0442 |
cp.plot()
Wszystkie poniższe wnioski odnoszą się do obserwacji podobnych wybranej obserwacji.
W przypadku tej obserwacji (oraz obserwacji jej podobnych) wiele zmiennych nie ma większego wkładu - profile Ceteris Paribus są linią horyzontalną. Dzieje się tak dla predyktorów median_age, total_bedrooms, population.
Ciekawe są profile dla szerokości (latitude) i długości geograficznej (longitude). Wynika z nich, że podobne nieruchomości jak ta wybrana, ale położona dalej na północ (większe latitude) miałyby mniejszą wartość, a na południe - większą. Podobnie na wschód (mniejsza wartość longitude - jesteśmy na półkuli zachodniej. Mniejsza długość odpowiada przesunięciu się bliżej GreenWich - bliżej Wielkiej Brytanii - na wschód) mamy mniejsza ceny, niż na zachodzie. To łatwo wytłumaczyć położeniem oceanu oraz większych aglomeracji.
Profil dla zmiennej households również niesie informację. Okazuje się, że do pewnego poziomu ta zmienna ma znaczenie i im więcej rodzin w okolicy, tym lepiej, ale później układa się na tym samym poziomie.
Profil dla zmiennej median_income zgadza się z intuicją - większe zarobki idą w parze z droższymi domostwami.
observation2 = predictors.loc[1024]
observation3 = predictors.loc[34]
observation4 = predictors.loc[2021]
observation5 = predictors.loc[2137]
cp2 = explainer.predict_profile(observation2)
cp3 = explainer.predict_profile(observation3)
cp4 = explainer.predict_profile(observation4)
cp5 = explainer.predict_profile(observation5)
Calculating ceteris paribus: 100%|████████████████████████████████████████████████████| 12/12 [00:00<00:00, 138.02it/s] Calculating ceteris paribus: 100%|████████████████████████████████████████████████████| 12/12 [00:00<00:00, 148.24it/s] Calculating ceteris paribus: 100%|████████████████████████████████████████████████████| 12/12 [00:00<00:00, 150.09it/s] Calculating ceteris paribus: 100%|████████████████████████████████████████████████████| 12/12 [00:00<00:00, 152.07it/s]
observation5
longitude 0.450199 latitude 0.449522 housing_median_age 0.607843 total_bedrooms 0.095142 population 0.047703 households 0.093421 median_income 0.133012 <1H OCEAN 0.000000 INLAND 1.000000 NEAR BAY 0.000000 NEAR OCEAN 0.000000 rooms_per_household 0.031770 Name: 2137, dtype: float64
cp5.plot()
Skupmy się na różnicach dla dwóch zmiennych: latitude oraz INLAND.
Zależność latitude od prognozowanej zmiennej jest nieco bardziej skomplikowana niż dla pierwszej obserwacji. Wciąż widzimy, że na południe byłoby drożej, ale na północ również (choć w mniejszym stopniu). Widzimy również o wiele większą zależność od zmiennej INLAND - gdyby nie była aktywna (nieruchomość nie stałaby w głębi lądu), cena by wzrosła. Oczywiście, trzeba uważać z taką interpretacją - aby nieruchomość nie stała w głębi lądu, trzeba by ją przesunąć - a to zmieniłoby wartości latitude i longitude, a pośrednio również inne.
Profile Ceteris Paribus są ładne i czytelne, ale ich interpretacja może być problematyczna. Wyciąganie jakichkolwiek wniosków z wykresów ma uzasadnienie tylko dla podobnych obserwacji jak ta, na podstawie której wykonano profil. Przez to łatwo o wnioski błędne (stosowanie ich do znacząco innych obserwacji) oraz wykonywanie niektórych z nich nie ma sensu (na przykład dla zmiennej INLAND - nie ma sensu mówić o sytuacji, gdy nie zmienia się położenie geograficzne, a zmienia się odległość od oceanu). Tym niemniej, dla niektórych zmiennych model zachowuje się przewidywalnie (przynajmniej w przypadku 5 sprawdzonych przeze mnie obserwacji) - mowa o zmiennych longitude oraz median_income. Jest to wynik wprost proszący o przyjrzenie się bliżej przy pomocy narzędzia patrzącego na cały model, a nie pojedyncze obserwacje.